package com.adrian.misc.fractal;

import java.awt.Color;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.Point;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import com.adrian.misc.fractal.color.ColorMap;
import com.adrian.misc.fractal.math.ComplexNumber;
import com.adrian.misc.fractal.math.RectArea;

public class Mandelbrot implements Fractal,Runnable{

	FractalParameters baseParam;
	FractalParameters currParam;
	ChangeListener fractalChangeListner;
	FractalCanvas canvas;
	double progress = 0.0;
	//TODO make porivate 
	public double[][] mathPlot;
	long numSetPoints=0;
	double setMean=0;
	double scnMin = 1.0;
	double scnMax = 0.0;
	double scnMinIn = 1.0;
	double scnMaxIn = 0.0;
	
	public Mandelbrot (MandelbrotCanvas c, ColorMap cm, int itr) {
		canvas = c;
		RectArea baseRect = new RectArea (new ComplexNumber(-2,-2), new ComplexNumber(2,2)) ;
		RectArea currRect = new RectArea (new ComplexNumber(-2,-2), new ComplexNumber(2,2)) ;
		System.out.println ("Height, width - " + c.getHeight() + "," + c.getPreferredSize().width);
		if (c.getPreferredSize().width > c.getPreferredSize().height) {
			baseParam = new FractalParameters(new AreaMapping ( baseRect ,new Point((c.getPreferredSize().width - c.getPreferredSize().height)/2,0), c.getPreferredSize().height, c.getPreferredSize().height), cm, itr);
			currParam = new FractalParameters(new AreaMapping ( currRect ,new Point((c.getPreferredSize().width - c.getPreferredSize().height)/2,0), c.getPreferredSize().height, c.getPreferredSize().height), cm, itr);			
		} else {
			baseParam = new FractalParameters(new AreaMapping ( baseRect ,new Point(0,(c.getPreferredSize().height - c.getPreferredSize().width)/2), c.getPreferredSize().width, c.getPreferredSize().width), cm, itr);
			currParam = new FractalParameters(new AreaMapping ( currRect ,new Point(0,(c.getPreferredSize().height - c.getPreferredSize().width)/2), c.getPreferredSize().width, c.getPreferredSize().width), cm, itr);			
		}
//		baseParam = new FractalParameters(new AreaMapping ( baseRect , c.getPreferredSize().width, c.getPreferredSize().height), cm, itr);
//		currParam = new FractalParameters(new AreaMapping ( currRect , c.getPreferredSize().width, c.getPreferredSize().height), cm, itr);
		mathPlot = new double[c.getPreferredSize().width][c.getPreferredSize().height];
		
		resetMathPlot (mathPlot);
		
		System.out.println("Size=====" + c.getPreferredSize().width +"," +c.getPreferredSize().height);
	}
	
	public Mandelbrot (MandelbrotCanvas c, FractalParameters param) {
		canvas = c;
		baseParam = new FractalParameters (param);
		currParam = new FractalParameters (param);
		
		mathPlot = new double[c.getPreferredSize().width][c.getPreferredSize().height];
		resetMathPlot(mathPlot);
	}
	private void resetMathPlot(double[][] mplot ){
		scnMax = 0.0;
		scnMin = 1.0;
		for(int i=0;i<mplot.length;i++) {
			for (int j=0;j<mathPlot[i].length;j++) {
			mathPlot[i][j]=1;
			}			
		}
		
	}
	public void run () {
	synchronized (this) { 
			if (canvas.isRendering()!=true) {
				canvas.setRendering(true);
			}else {
				return;
			}
		}
		canvas.setRendering(true);
		render (canvas.getBufferedGraphics());
	}
	private void render( Graphics g) {
		AreaMapping am = currParam.fAreaMap;
		int width = am.width;
		int height = am.height;
		int xStart = am.xStart;
		int yStart = am.yStart;
		int iterations = currParam.iterations;
		int i ;
		scnMax = 0.0;
		scnMin = 1.0;
		scnMaxIn = 0.0;
		scnMinIn = 1.0;
		numSetPoints=0;
		setMean = 0.0;
		ComplexNumber z0 = new ComplexNumber();
		ComplexNumber zn = new ComplexNumber();
		if (iterations == -1) {
			iterations = 22*(int)Math.pow( ((4.0/am.mathMap.width) * 100), .333);
		}
		System.out.println("Render of applet xstart=" + xStart+ "width" + width);
		System.out.println("Render of applet ystart=" + yStart+ "height" + height);
		System.out.println("Render:Start = " +am + "iterations = " +iterations);
//		int size = width < height ? width:height;
		for (int x=xStart;x<xStart + width;x++) {
			for (int y=yStart;y<yStart + height;y++) {
				am.getCCoord(x, y, z0);
				zn.set(z0);
				//if ( (x%50 ==0) && (y%50==0)) { System.out.println(zn);}
				for (i=0;i<iterations;i++) {
					zn.sqr();
					zn.add(z0);
					if (zn.abs() > 2.0) break;
				}
				try { 
						if ( i < iterations) {
							mathPlot[x][y]=(double)i/(double)iterations;
							if (mathPlot[x][y] < scnMin) scnMin = mathPlot[x][y];
							if (mathPlot[x][y] > scnMax) scnMax = mathPlot[x][y];
						} else {
							mathPlot[x][y] = (2.0-zn.abs())/2.0;
							setMean+=mathPlot[x][y];
							if (mathPlot[x][y] < scnMinIn) scnMinIn = mathPlot[x][y];
							if (mathPlot[x][y] > scnMaxIn) scnMaxIn = mathPlot[x][y];
							mathPlot[x][y] = -1*mathPlot[x][y] ;							
							numSetPoints++;
						}
				} catch (Exception e) {
					e.printStackTrace();
					System.out.println("X="+ x +", Y="+y+" i="+i+ ",area=" + am);
					return;
				}
			//	System.out.println("Exit iteration" + i + z0);				
			}
			progress = (double)x/(double)width;
			if (fractalChangeListner!=null)  {
				fractalChangeListner.stateChanged(new ChangeEvent(this));
			}
		}
		setMean = (setMean/numSetPoints);
		progress = 1.0;
		draw(g);
	}
private double sd(){
	double sumVar = 0.0;
	int width = ((Component)canvas).getPreferredSize().width;
	int height = ((Component)canvas).getPreferredSize().height;
	for (int x=0;x<width;x++) {
		for (int y=0;y<height;y++) {
			if (mathPlot[x][y]<0) sumVar += ((mathPlot[x][y]+ setMean)*(mathPlot[x][y]+ setMean));
		}
	}	
	return  Math.sqrt((sumVar/numSetPoints));
}
	public void draw(Graphics g) {
		AreaMapping am = currParam.fAreaMap;
		ColorMap cMap = currParam.colMap;
		int width = ((Component)canvas).getPreferredSize().width;
		int height = ((Component)canvas).getPreferredSize().height;
		double scnMinInSD = setMean - 3*sd();
		double scnMaxInSD = setMean + 3*sd();
		double tmpNormal = 0.0;
		Color fg = new Color(0,0,0);
		g.setColor(fg);
		g.clearRect(am.xStart, am.yStart, am.width, am.height);
		System.out.println("MEAN == " +setMean + ", STD DEV=" + sd());
		System.out.println("Mathplot" + mathPlot.length+ " Adaptive = " + currParam.adaptive + "ScnMin,scnMax =" + scnMin +"," +scnMax );
		System.out.println ("@@@ Xmx,Xmin=" + scnMax +"," +scnMin);
		System.out.println ("@@@ Internal Xmx,Xmin=" + scnMaxIn +"," +scnMinIn);
		for (int x=0;x<width;x++) {
			for (int y=0;y<height;y++) {
				if ((currParam.innerColor) && (mathPlot[x][y] < 0) ){
					//tmpNormal =  currParam.adaptive?((-1*mathPlot[x][y]- scnMinIn)/(scnMaxIn-scnMinIn)):-1*mathPlot[x][y];
					tmpNormal =  currParam.adaptive?((-1*mathPlot[x][y]- scnMinInSD)/(scnMaxInSD-scnMinInSD)):((-1*mathPlot[x][y]- scnMinIn)/(scnMaxIn-scnMinIn));
				} else {
					tmpNormal =  currParam.adaptive?((mathPlot[x][y]- scnMin)/(scnMax-scnMin)):mathPlot[x][y];					
				}
				if (tmpNormal < 0.0) {tmpNormal = 1.0;}
				//tmpNormal = currParam.adaptive?((mathPlot[x][y]-scnMin)/(scnMax-scnMin)):mathPlot[x][y];
				try {
					g.setColor(cMap.getColor(tmpNormal) );
				} catch (Exception e) {
					System.out.println("DBG = " + tmpNormal +" x,y=" + x +", " +y); 
				}
				g.drawLine(x, y, x, y);
			}
		}
		canvas.setRendering(false);
		canvas.updateRefreshBuffer();
		canvas.repaint();
		canvas.setSync(true);
	}


	public void reset() {
		resetMathPlot(mathPlot);
		currParam = new FractalParameters(baseParam);
	}


	public FractalParameters getFractalParam() {
		return currParam;
	}


	public void addChangeListener(ChangeListener cl) {
		fractalChangeListner = cl;
	}


	public double getState() {
		return progress;
	}

	public void setAdaptive(boolean adptive) {
		currParam.adaptive = adptive;
	}

	public void setInnerColor(boolean inrColor) {
		currParam.innerColor = inrColor;		
	}

}
